﻿namespace NKernel.Core.Provider
{
    using System;
    using System.Collections.Generic;

    using Castle.Core;
    using Castle.Windsor;

    using Impl;

    using log4net;

    /// <summary>
    /// Description of WindsorProvider.
    /// </summary>
    public class WindsorProvider : IKernelProvider
    {
        #region Fields

        /// <summary>
        /// Represents the queue in which the components are instantiated.
        /// </summary>
        private readonly Queue<string> componentInstantiationQueue;

        /// <summary>
        /// logging instance
        /// </summary>
        private readonly ILog logger = LogManager.GetLogger(typeof (WindsorProvider));

        private IWindsorContainer container;

        #endregion Fields

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="WindsorProvider"/> class.
        /// </summary>
        public WindsorProvider()
        {
            componentInstantiationQueue = new Queue<string>();
        }

        #endregion Constructors

        #region Methods

        /// <summary>
        /// Automatically assembles the components
        /// </summary>
        /// <param name="componentDescriptors"></param>
        /// <param name="kernel">Instance of the IKernel</param>
        /// <returns>
        /// List of component objects ordered according to instantiation sequence
        /// </returns>
        public IList<IComponentObject> AssembleComponents(IList<ComponentDescriptor> componentDescriptors, IKernel kernel)
        {
            if (componentDescriptors == null)
            {
                const string msg = "List of component descriptors is null!!";
                logger.ErrorFormat(msg);
                throw new ArgumentNullException(msg);
            }

            if(logger.IsDebugEnabled)
            {
                logger.DebugFormat("Number of components to assemble: {0}", componentDescriptors.Count);
            }

            IList<IComponentObject> componentObjects = new List<IComponentObject>();
            container = new WindsorContainer();
            container.Kernel.AddComponentInstance("IKernel", typeof (IKernel), kernel);

            if(logger.IsDebugEnabled)
            {
                logger.DebugFormat("Added Kernel instance to container: {0}", kernel);
            }
                        
            //Add an event listener in order to determine the component creation order
            container.Kernel.ComponentCreated += KernelComponentCreated;

            //Add the component details into the container
            foreach (ComponentDescriptor componentDescriptor in componentDescriptors)
            {
                string name = componentDescriptor.ComponentName;
                string intf = componentDescriptor.ComponentInterface;
                string impl = componentDescriptor.ComponentImplementation;

                logger.DebugFormat("Processing component {0} using {1}, {2}", name, intf, impl);

                try
                {
                    container.AddComponent(name,
                                   componentDescriptor.Assembly.GetType(intf),
                                   componentDescriptor.Assembly.GetType(impl));
                }
                catch (Exception exception)
                {
                    logger.ErrorFormat("Problem encountered while adding component {0} to the container using {1}, {2}", name, intf, impl);
                    logger.ErrorFormat("Check that all component dependencies have been included", componentDescriptor.ComponentName, exception);
                    logger.Error("Error stack: ", exception);
                }
            }

            //Now use the container to resolve all component dependencies and obtain a resolve to the implementation
            DoInstantiate(componentDescriptors, componentObjects);

            //Now sort the components in the order of container instantiation
            IList<IComponentObject> orderedComponentObjects = DoOrder(componentObjects);

            componentObjects.Clear();
            componentInstantiationQueue.Clear();

            return orderedComponentObjects;
        }

        /// <summary>
        /// Accessor method to get the implementation of an interface
        /// </summary>
        /// <typeparam name="T">Fully-qualified name of the interface type</typeparam>
        /// <returns>interface instance object</returns>
        public T GetInstance<T>()
        {
            return container.Resolve<T>();
        }

        private void DoInstantiate(IEnumerable<ComponentDescriptor> componentDescriptors, ICollection<IComponentObject> componentObjects)
        {
            foreach (ComponentDescriptor componentDescriptor in componentDescriptors)
            {
                if (logger.IsDebugEnabled)
                {
                    logger.Debug("Using assembly for instance instantiation: " + componentDescriptor.Assembly);
                }

                object instance = null;
                try
                {
                    string intf = componentDescriptor.ComponentInterface;
                    Type interfaceType = componentDescriptor.Assembly.GetType(intf);
                    if (interfaceType == null)
                    {
                        logger.ErrorFormat("Why is type value for {0} null?", intf);
                        continue;
                    }
                    instance = container.Resolve(interfaceType);
                }
                catch (Exception exception)
                {
                    logger.Error("Container exception:", exception);
                }

                if (instance == null)
                {
                    logger.Warn("Problem encountered in instantiating interface definition: " +
                                componentDescriptor.ComponentInterface);
                }
                else
                {
                    var componentObject = new ComponentObject(componentDescriptor)
                                              {
                                                  ComponentInterfaceInstance = instance
                                              };
                    logger.Info("Component interface instance: " + instance);
                    componentObjects.Add(componentObject);
                }
            }
        }

        private IList<IComponentObject> DoOrder(IEnumerable<IComponentObject> componentObjects)
        {
            IList<IComponentObject> orderedComponentObjects = new List<IComponentObject>();
            foreach (string instantiatedComponent in componentInstantiationQueue)
            {
                foreach (var component in componentObjects)
                {
                    if(component.ComponentName == instantiatedComponent)
                    {
                        orderedComponentObjects.Add(component);
                        break;
                    }
                }
            }
            return orderedComponentObjects;
        }

        private void KernelComponentCreated(ComponentModel model, object instance)
        {
            string componentName = model.Name;
            if (logger.IsDebugEnabled)
            {
                logger.Debug("Instance created: " + instance + " Model: " + componentName);
            }
            componentInstantiationQueue.Enqueue(componentName);
        }

        #endregion Methods
    }
}